Skip to content

fix(android): release data-binding HybridObject native resources#302

Merged
mfazekas merged 3 commits into
mainfrom
fix/android-databinding-hybridobject-lifecycle
Jul 14, 2026
Merged

fix(android): release data-binding HybridObject native resources#302
mfazekas merged 3 commits into
mainfrom
fix/android-databinding-hybridobject-lifecycle

Conversation

@mfazekas

@mfazekas mfazekas commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Each HybridViewModel*Property / HybridViewModelInstance is a small JS object that pins a JNI global reference plus a native Rive object. Two issues with how those were managed:

dispose() was hidden. A property both extends its generated *Spec (a HybridObject) and delegates BaseHybridViewModelProperty via by. Both declare dispose(), so the delegate's won and hid HybridObject.dispose() (the "hides supertype override" warning). Calling dispose() only cancelled the listener flow and never freed the native part / global ref. Each property now overrides dispose() to do both (removeListeners() + super<*Spec>.dispose()).

memorySize was 0. Hermes schedules GC on JS-heap pressure, so these tiny-looking but native-heavy wrappers were never collected under churn — the JNI global reference table (cap 51200) filled up and the process aborted with JNI ERROR: global reference table overflow. Overriding memorySize gives the GC the true cost so it collects them in time.

Reproducer: added a toggleable Android JNI Global-Ref Overflow page under Reproducers — Start the stress and on an unfixed build the app dies within seconds; with this fix the counter climbs indefinitely. (Same operations as the local harness stress, which aborts every run without the fix and 0 with it.)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes Android data-binding HybridObject native resource retention by ensuring dispose() frees both listener resources and the underlying Nitro/HybridObject native resources, and by providing Hermes with a realistic memorySize for view-model wrapper objects so GC triggers under native-pressure churn.

Changes:

  • Override dispose() in HybridViewModel*Property wrappers that delegate BaseHybridViewModelProperty so they call both removeListeners() and super<*Spec>.dispose().
  • Add VIEW_MODEL_HYBRID_MEMORY_SIZE and override memorySize on the main view-model wrappers to improve Hermes GC behavior under churn.
  • Add an example reproducer page to stress view-model property/instance accessors and validate the fix against JNI global-ref overflow.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
example/src/reproducers/AndroidGlobalRefOverflow.tsx Adds a manual stress reproducer for Android JNI global-ref overflow under view-model accessor churn.
android/src/main/java/com/margelo/nitro/rive/BaseHybridViewModelProperty.kt Introduces a shared VIEW_MODEL_HYBRID_MEMORY_SIZE constant for Hermes GC pressure hinting.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelTriggerProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelStringProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelNumberProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelListProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelInstance.kt Sets memorySize for view-model instances to reflect native/JNI cost.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelImageProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelEnumProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelColorProperty.kt Ensures dispose() frees native resources and sets memorySize.
android/src/main/java/com/margelo/nitro/rive/HybridViewModelBooleanProperty.kt Ensures dispose() frees native resources and sets memorySize.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread example/src/reproducers/AndroidGlobalRefOverflow.tsx
@mfazekas mfazekas force-pushed the fix/android-databinding-hybridobject-lifecycle branch from f060b5f to a08cdc3 Compare July 3, 2026 14:27

@HayesGordon HayesGordon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks!

mfazekas added 3 commits July 14, 2026 09:31
Each HybridViewModel*Property / HybridViewModelInstance is a small JS object
that pins a JNI global reference plus a native Rive object. Two problems:

- dispose() was hidden: a property both extends its generated *Spec (a
  HybridObject) and delegates BaseHybridViewModelProperty via 'by'; both
  declare dispose(), so the delegate's won and hid HybridObject.dispose().
  Calling dispose() only cancelled the listener flow, never freeing the
  native part / global ref. Each property now overrides dispose() to do both.

- memorySize was 0: Hermes GCs on JS-heap pressure, so it never collected
  these tiny-looking but native-heavy wrappers under churn, and the JNI
  global reference table (cap 51200) overflowed. Report memorySize so the
  GC collects them in time.
Auto-registers under Reproducers. Churns view-model property/instance
accessors (each pins a JNI global ref); without the memorySize fix the
Android global reference table (cap 51200) overflows within seconds, with
it the counter climbs indefinitely.
… wrappers

HybridViewModelArtboardProperty and HybridViewModel also pin a JNI global
ref but were left with the default (~0) memorySize, so Hermes could let
them accumulate and overflow the global reference table under churn. Give
them the same VIEW_MODEL_HYBRID_MEMORY_SIZE hint as the other wrappers.
@mfazekas mfazekas force-pushed the fix/android-databinding-hybridobject-lifecycle branch from a08cdc3 to a5a8f98 Compare July 14, 2026 07:32
@mfazekas mfazekas enabled auto-merge (squash) July 14, 2026 07:32
@mfazekas mfazekas merged commit 4281519 into main Jul 14, 2026
9 checks passed
@mfazekas mfazekas deleted the fix/android-databinding-hybridobject-lifecycle branch July 14, 2026 08:00
mfazekas pushed a commit that referenced this pull request Jul 14, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.4.16](v0.4.15...v0.4.16)
(2026-07-14)


### Bug Fixes

* **android:** release data-binding HybridObject native resources
([#302](#302))
([4281519](4281519))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants